﻿Relational Active Record
========================

כבר ראינו ולמדנו כיצב להשתמש ב Active Record (AR) כדי לשלוף מידע מטבלה במסד הנתונים. בחלק זה, אנו נסביר כיצד להשתמש ב AR כדי לאחד כמה טבלאות המקושרות אחת לשנייה ולהחזיר את סט הנתונים המאוחד.

בכדי להשתמש בקישור AR, מומלץ להגדיר את המפתחות הראשיים של הטבלאות העומדות להיות מאוחדות עם טבלאות אחרות כבר מראש. הגדרתם תעזור לשמירת ההמשכיות והאחדות של המידע המקושר.

כדי לפשט את ההסבר, אנו נשתמש בתרשים מסד הנתונים המוצג בדיאגרמה הבאה בכדי להדגים את הדוגמאות בחלק זה.

![דיאגרמה](er.png)

» Info|מידע: תמיכה במפתחות ראשיים חיצוניים תלויה ב DBMS בו משתמשים. SQLite « 3.6.19 אינו תומך במפתחות ראשיים חיצוניים, אך עדיין ניתן להגדיר את המפתחות החיצוניים בעת יצירת הטבלה.

הצהרת קישורי טבלאות
----------------------

לפני שנשתמש ב AR בכדי לבצע שאילתה מקושרת, אנו צריכים ליידע את ה AR כיצד מחלקת AR אחת מקושרת עם אחרת.

הקישור בין שני מחלקות AR משתייך ישירות לקישורים בין הטבלאות המיוצגות על ידי מחלקות ה AR. מנקודת מבט של מסד נתונים, קישור בין שני טבלאות A ו B ניתן להגדרה על ידי שלושה סוגים: יחיד-ל-רבים (`one-to-many` לדוגמא `tbl_user` ו `tbl_post`), יחיד-ל-יחיד ( `one-to-one` לדוגמא `tbl_user` ו `tbl_profile`) ו רבים-ל-רבים (`many-to-many` לדוגמא `tbl_category` ו `tbl_post`). ב AR ישנם ארבע סוגים של קישורים:

- `BELONGS_TO`: במידה והקישור בין טבלה א' לבין טבלה ב' הינה יחיד-ל-רבים, אז ב' שייך ל א' (לדוגמא `Post` שייך ל `User`);

- `HAS_MANY`: במידה והקישור בין טבלה א' לבין טבלה ב' הינה יחיד-ל-רבים, אז א' מכיל הרבה מ ב' (לדוגמא `User` מכיל הרבה `Post`);

- `HAS_ONE`: זהו מקרה מיוחד של `HAS_MANY` איפה א' מכיל לכל היותר ב' אחד (לדוגמא `User` מכיל לכל היותר `Profile` אחד);

- `MANY_MANY`: זה מתייחס לקישור של רבים-ל-רבים במסד נתונים. טבלה אסוציאטיבית נחוצה בכדי לחלק את הקישור רבים-ל-רבים לקישור יחיד-ל-רבים, מאחר ומרבית ה DBMS אינם תומכים בקישור מסוג רבים-ל-רבים כברירת מחדל. בדוגמא של תרשים מסד הנתונים, הטבלה `tbl_post_category` משרתת מטרה זו. בטרמינולוגיה של AR אנו יכולים להסביר את `MANY_MANY` כקומבינציה של `BELONGS_TO` ו `HAS_MANY`. לדוגמא, `Post` שייך לכמה `Category` ו `Category` מכיל הרבה `Post`.

הגדרת קישורים ב AR כרוכה בדריסה של המתודה [relations|CActiveRecord::relations] של המחלקה [CActiveRecord]. המתודה מחזירה מערך של הגדרות קישורים. כל אלמנט במערך קישור בודד בפורמט הבא:

~~~
[php]
'VarName'=»array('RelationType', 'ClassName', 'ForeignKey', ...additional options)
~~~

`varName` מייצג את שם הקישור; `RelationType` מייצג את סוג הקישור, הוא יכול להיות אחד מהערכים: `self::BELONGS_TO`, `self::HAS_ONE`, `self::HAS_MANY` ו
`self::MANY_MANY`; `ClassName` הינו שם מחלקת ה AR הקשור למחלקת ה AR הנוכחית; ו `ForeignKey` מייצג את המפתח(ות) הראשיים השייכים לקישור זה. אפשרויות נוספות ניתנות להגדרה בסוף האלמנט לכל קישור (הסבר בהמשך).

הקוד הבא מדגים כיצד אנו מצהירים על הקישורים בין המחלקות `Post` ו `User`.

~~~
[php]
class Post extends CActiveRecord
{
    ......

    public function relations()
    {
        return array(
            'author'=»array(self::BELONGS_TO, 'User', 'author_id'),
            'categories'=»array(self::MANY_MANY, 'Category',
                'tbl_post_category(post_id, category_id)'),
        );
    }
}

class User extends CActiveRecord
{
    ......

    public function relations()
    {
        return array(
            'posts'=»array(self::HAS_MANY, 'Post', 'author_id'),
            'profile'=»array(self::HAS_ONE, 'Profile', 'owner_id'),
        );
    }
}
~~~

» Info|מידע: מפתח ראשי יכול להיות מורכב, המכיל שני עמודות או יותר. במקרה זה, אנו צריכים לאחד את שמות העמודות ולהפריד אותם בעזרת פסיק או רווח. עבור סוג קישור `MANY_MANY`, שם הטבלה המקושרת צריכה להיות מוגדרת במפתח החיצוני. לדוגמא, הקישור `categories` ב `Post` מוגדרת באמצעות המפתח החיצוני `tbl_post_category(post_id, category_id)`.

ההצהרה של קישורים במחלקת ה AR מוסיפה בצורה עקיפה מאפיינים למחלקה אשר מהווים ייצוג של הקישורים עצמם. לאחר ביצוע שאילתה המקשרת בין טבלאות, המאפיין הנלמד יאוכלס על ידי הקישור של מחלקת ה AR. לדוגמא, אם `author$` מייצג אובייקט AR של `User`, אנו נוכל להשתמש ב `author-»posts$` בכדי לגשת לקישור של אובייקט ה `Post`.

ביצוע שאילתות מקושרות
---------------------------

הדרך הפשוטה ביותר לביצוע שאילתה מקושרת היא על ידי קריאת מאפיין הקישור של אובייקט AR. במידה והמאפיין לא נקרא קודם לכן, תתבצע שאילתה מקושרת, המאחדת את שני הטבלאות המקושרות ומסננת בעזרת המפתח הראשי של אובייקט ה AR הנוכחי. תוצאת השאילתה תשמר במאפיין כאובייקט(ים) של מחלקת ה AR המקושרת. שיטה זו ידועה בשמה כ *טעינה עצלה* (lazy loading), כלומר, השאילתה המקושרת מתבצעת רק במידה וזמן שניגשים לאבוייקטים המקושרים (זאת אומרת קוראים את המאפיינים של הקישורים בין הטבלאות). הדוגמא הבאה מציגה כיצד להשתמש בשיטה זו:

~~~
[php]
// קבלת ההודעה עם id = 10
$post=Post::model()-»findByPk(10);
// קבלת מידע אודות מפרסם ההודעה, כאן תתבצע שאילתה מקושרת
$author=$post-»author;
~~~

» Info|מידע: במידה ולא קיים אובייקט לקישור מסויים, אותו מאפיין במחלקת ה AR המקשרת יכול להיות null או מערך ריק. בעבור קישורי טבלאות מסוג `BELONGS_TO` ו `HAS_ONE`, התוצאה תיהיה null; בעבור קישורי טבלאות מסוג `HAS_MANY` ו `MANY_MANY`, התוצאה תיהיה מערך ריק.
זכור שקישורי הטבלאות מסוג `HAS_MANY` ו `MANY_MANY` מחזירים מערך של אובייקטים, תצטרכו לרוץ על כל התוצאות לפני שיהיה ניתן לגשת לכל אחד כאובייקט, אחרת, תזרק שגיאה של ניסיון לקריאה של מאפיינים לאובייקט שלא קיים.

גישת ה*טעינה עצלה* נוחה מאוד לשימוש, אך היא לא יעילה במקרים מסויימים. לדוגמא, במידה ואנו רוצים לגשת למידע אודות המפרסם של `N` הודעות, שימוש ב*טעינה עצלה* תבצע `N` שאילתות מקשרות. אנו נצטרך לנקוט בגישה של *טעינה נלהבת* (eager loading) במקרים כאלו.

גישת  ה*טעינה נלהבת* מחזיר את האובייקטים המקושרים לאובייקט AR הראשי. ניתן לבצע זאת על ידי שימוש במתודה [with()|CActiveRecord::with] בשילוב של אחת מפקודות ה [find|CActiveRecord::find] או [findAll|CActiveRecord::findAll] במחלקת AR. לדוגמא,

~~~
[php]
$posts=Post::model()-»with('author')-»findAll();
~~~

הקוד למעלה יחזיר מערך של אובייקטים של `Post`. בניגוד לגישה של טעינה עצלה, המאפיין `author` בכל אובייקט של `Post` כבר מאוכלס וקיים בשילוב של האובייקט של `User` לפני שאנו אפילו ניגשים לקרוא את המאפיין.
במקום לבצע שאילתה מקשרת לכל הודעה, גישת הטעינה הנלהבת מחזירה את כל ההודעות ביחד עם המידע אודות המפרסם שלה בשאילתה אחת!

ניתן להגדיר מספר רב של שמות קישורים במתודה [with|CActiveRecord::with] וגישת הטעינה הנלהבת תחזיר את כולם במכה אחת. לדוגמא, הקוד הבא מחזיר הודעות ביחד עם המידע אודות המפרסם שלהם והקטגוריה בה הם פורסמו:

~~~
[php]
$posts=Post::model()-»with('author','categories')-»findAll();
~~~

כמו כן, אנו יכולים לקנן בין קישורים בעזרת הטעינה הנלהבת. במקום רשימה של שמות קישורים, אנו מעבירים רפרזנטציה היררכית של שמות הקישורים למתודה [with|CActiveRecord::with], כפי שמוצג בדוגמא הבאה,

~~~
[php]
$posts=Post::model()-»with(
    'author.profile',
    'author.posts',
    'categories')-»findAll();
~~~

הדוגמא למעלה תחזיר את כל ההודעות ביחד עם המידע אודות המפרסם והקטגוריות בהם הם פורסמו. בנוסף היא תחזיר את המידע אודות הפרופיל של המשתמש וההודעות שלו.

החל מגרסא 1.1.0, ניתן להשתמש בגישת הטעינה הנלהבת בעזרת הגדרת המאפיין [CDbCriteria::with], כפי שמוצג בדוגמא:

~~~
[php]
$criteria=new CDbCriteria;
$criteria-»with=array(
    'author.profile',
    'author.posts',
    'categories',
);
$posts=Post::model()-»findAll($criteria);
~~~

או

~~~
[php]
$posts=Post::model()-»findAll(array(
    'with'=»array(
        'author.profile',
        'author.posts',
        'categories',
    )
);
~~~


אפשרויות שאילתות מקושרות
------------------------

ציינו שניתן להגדיר אפשרויות נוספות בהגדרה של קישורים. אפשרויות אלו, המוגדרות כזוגות של שמות מפתחות וערכים, מוגדרות כדי לשנות את השאילתה המקושרת. סיכום של אפשרויות אלו נמצא למטה.

- `select`: רשימה של עמודות שאותם צריכים לשלוף ממחלקת ה AR המקושרת. ברירת המחדל הינה '*', שזה אומר כל העמודות, שמות העמודות באפשרות זו צריכות להיות יחודיות.

- `condition`: סעיף ה `WHERE`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות.

- `params`: הפרמטרים שצריכים להיות תחומים לשאילתת ה SQL שתווצר. אפשרות זו צריכה להיות מוגדרת כמערך של מפתחות וערכים. אפשרות זו קיימת מגרסאות 1.0.3 ומעלה.

- `on`: סעיף ה `ON`. התנאי המוגדר כאן יצורף לתנאי של הקישור בעזרת האופרטור `AND`. שמות העמודות באפשרות זו צריכות להיות יחודיות. אפשרות זו לא תקפה לסוג קישור `MANY_MANY`. אפשרות זו קיימת מגרסאות 1.0.2 ומעלה.

- `order`: סעיף ה `ORDER BY`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות.

- `with`: רשימה של אובייקטים המקושרים לאובייקט זה ושיש צורך בלטעון אותם ביחד עם אובייקט זה. דע לך ששימוש לא נכון באפשרות זו יכול לגרום ללולאה אין סופית.

- `jointType`: סוג האיחוד לקישור זה. כברירת מחדל הוא מוגדר ל `LEFT OUTER JOIN`.

- `alias`: שם קיצור לטבלה הקשורה לקישור זה. אפשרות זו קיימת מגרסא 1.0.1. כברירת מחדל היא מוגדרת ל null, שאומר שהקיצור לטבלה הוא זהה לשם הקישור עצמו.

- `together`: במידה והטבלה המקושרת עם קישור זה מאולצת לאיחוד ביחד עם הטבלה הראשית ושאר הטבלאות. אפשרות זו תקפה רק לקישורים מסוג HAS_MANY ו MANY_MANY. במידה ואפשרות זו מוגדרת ל false, הטבלה המקושרת ל HAS_MANY או MANY_MANY תאוחד עם הטבלה הראשית בשאילתה נפרדת, אשר יכול לשפר את הביצועים הכוללים של השאילתה מאחר ופחות נתונים כפולים יוחזרו. כברירת מחדל האפשרות מוגדרת ל true.
למידע נוסף קרא את החלק "ביצועי שאילתה מקושרת". אפשרות זו קיימת מגרסא 1.0.3 ומעלה.

- `group`: סעיף ה `GROUP BY`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות. אפשרות זו קיימת מגרסא 1.0.1 ומעלה.

- `having`: סעיף ה `HAVING`. כברירת מחדל הוא ריק. שמות העמודות באפשרות זו צריכות להיות יחודיות. אפשרות זו קיימת מגרסא 1.0.1 ומעלה.

- `index`: שמות העמודות שערכיהם צריכות להיות משומשות כמפתחות למערך המאחסן את האובייקטים המקושרים. ללא הגדרת אפשרות זו, אובייקט מקושר ישתמש במפתחות מספריים מ-0 למערך של האובייקטים המקושרים. אפשרות זו ניתנת להגדרה רק על קישורים מסוג `HAS_MANY` ו `MANY_MANY`. אפשרות זו קיימת מגרסא 1.0.7 ומעלה.

בנוסף, האפשרויות הבאות קיימות גם כן לקישורים מסויימים במהלך טעינה עצלה:

- `limit`: הגבלת מספר השורות לבחירה. אפשרות זו לא תקפה לקישור מסוג `BELONGS_TO`.

- `offset`: מאיזה שורה להתחיל לשלוף את הנתונים. אפשרות זו לא תקפה לקישור מסוג `BELONGS_TO`.

למטה אנו עורכים את הגדרת הקישור של `posts` במחלקה של `User` על ידי הוספת כמה מהאפשרויות למעלה:

~~~
[php]
class User extends CActiveRecord
{
    public function relations()
    {
        return array(
            'posts'=»array(self::HAS_MANY, 'Post', 'author_id',
                            'order'=»'posts.create_time DESC',
                            'with'=»'categories'),
            'profile'=»array(self::HAS_ONE, 'Profile', 'owner_id'),
        );
    }
}
~~~

כעת, ברגע שניגש ל `author-»posts$`, אנו נקבל את ההודעות של המפרסם מסודרים לפי תאריך הפרסום שלהם בסדר יורד. כל אובייקט של הודעה מכיל גם את הקטגוריות טעונות בתוכו.

שמות יחודיים לעמודות
---------------------------

כששם העמודות מופיע בשני טבלאות או יותר אשר מקושרות ביחד, יש צורך בלזהות אותם צבורה יחודית. זה נעשה על ידי מתן קידומת לעמודה עם השם המקוצר של הטבלה בה היא נמצאת.

בשאילתת AR מקושרת, השם המקוצר לטבלה הראשית הינו מוגדר מראש בתור 't' בזמן ששם המקוצר לטבלה המקושרת כברירת מחדל הוא זהה לשמו של הקישור שהוגדר. לדוגמא, בביטוי הבא, השמות המקוצרים לטבלאות `Post` ו `Comment` הינו `t` ו  `comments`, בהתאמה:

~~~
[php]
$posts=Post::model()-»with('comments')-»findAll();
~~~

כעת, נניח שישנה עמודה בשם `create_time` בשני הטבלאות של `Post` ו `Comment` המייצג את הזמן פרסום ההודעה וזמן פרסום התגובה, ואנו נרצה לשלוף את ההודעות ביחד עם התגובות שלהם וסידור ההודעות על פי תאריך הפרסום שלהם קודם ולאחר מכן סידור התגובות על פי תאריך הפרסום שלהם. אנו נצטרך לתת שם יחודי לעמודה `create_time` בצורה הבאה:

~~~
[php]
$posts=Post::model()-»with('comments')-»findAll(array(
    'order'=»'t.create_time, comments.create_time'
));
~~~

» Note|הערה: ההתנהגות של שמות עמודות יחודיים השתנתה החל מגרסא 1.1.0. קודם לכן בגרסאות *.1.0, כברירת מחדל Yii אוטומטית יצרה שם מקוצר לכל טבלה מקושרת, ואנו היינו צריכים להשתמש בקידומת של `.??` כדי להתייחס לשם המקוצר שנוצר אוטומטית. בנוסף, בגרסאות *.1.0, השם המקוצר שם הטבלה הראשית הוא שם הטבלה עצמה.

אפשרויות דינאמיות לשאילתות מקושרות
--------------------------------

החל מגרסא 1.0.2, אנו יכולים להשתמש באפשרויות דינאמיות לשאילתות מקושרות במתודה [with|CActiveRecord::with] וגם על ידי שימוש באפשרות `with`. האפשרויות הדינאמיות ידרוס את האפשרויות הקיימות שהוגדרו במתודה [relations|CActiveRecord::relations]. לדוגמא, בעזרת המודל `User` המצויין למעלה, במידה ואנו נרצה להשתמש בגישה של טעינה נלהבת כדי להחזיר את ההודעות השייכות למפרסם *בסדר עולה* (אפשרות ה `order` בהגדרות של הקישור מוגדרת בסדר יורד), אנו יכולים לבצע את הפעולה הבאה:

~~~
[php]
User::model()-»with(array(
    'posts'=»array('order'=»'posts.create_time ASC'),
    'profile',
))-»findAll();
~~~

החל מגרסא 1.0.5, אפשרויות דינאמיות של שאילתות מקושרות ניתנות לשימוש בעת שימוש בגישה של טעינה עצלה בכדי לבצע שאילתה מקושרת. בכדי לעשות זאת, אנו נקרא למתודה ששמה הוא זהה לשם של הקישור ונעביר את האפשרויות הדינאמיות למתודה כפרמטר. לדוגמא, הקוד הבא מחזיר את הודעות המשתמש שהסטטוס שלהם הוא 1, `status` שווה ל 1:

~~~
[php]
$user=User::model()-»findByPk(1);
$posts=$user-»posts(array('condition'=»'status=1'));
~~~


ביצועי שאילתה מקושרת
----------------------------

בפי שתארנו כבר למעלה, השימוש בטעינה נלהבת בעיקר הוא במקרה שאנו צריכים לגשת לאובייקטים מקושרים רבים. הוא יוצר שאילתת SQL גדולה ומורכבת על ידי חיבור כל הטבלאות המקושרות. שאילתת SQL גדולה עדיפה בהרבה מקרים מאחר והיא מפשטת את השינון בהתבסס על העמודות בטבלה מקושרת. למרות, שהיא לא כל כך יעילה במקרים מסויימים.

נקח כדוגמא מקרה בו אנו צריכים למצוא את ההודעות האחרונות ביחד עם התגובות שלהם. בהנחה שלכל הודעה יש 10 תגובות, שימוש בשאילתת SQL אחת גדולה, יחזיר בחזרה הרבה מידע מיותר מאחר וכל הודעה תחזור על עצמה על כל תגובה שפורסמה בה. עכשיו ננסה גישה אחרת: קודם אנו מבצעים שאילתה לקבלת ההודעות האחרונות, ולאחר מכן אנו מכן שאילתה לקבלת התגובות בתוכה. בגישה חדשה זו, אנו צריכים לבצע שני שאילתות. היתרון הוא שאנו לא מקבלים מידע מיותר בתוצאות השאילתה.

אז איזה גישה יותר יעילה? אין תשובה מוחלטת. הרצת שאילתה אחת גדולה יכול להיות יותר יעיל מאחר והיא צורכת פחות משאבים ב DBMS על מנת לנתח ולהריץ את שאילתת ה SQL. מצד שני, שימוש בשאילתת SQL אחת, אנו מקבלים בסופו של דבר הרבה תוכן מיותר שדורש יותר זמן בקריאה ועיבוד שלו.

מסיבה זו, Yii מספקת את האפשרות של `together` בשאילתות כדי שנוכל לבחור בין הגישות השונות לפי הצורך. כברירת מחדל, Yii מאמצת את הגישה הראשונה, כלומר, ביצוע שאילתה אחת גדולה בכדי לבצע טעינה נלהבת. אנו יכולים להגדיר את האפשרות של `together` לערך false בהגדרות של הקישור בכדי שכמה טבלאות יאוחדו בשאילתה נפרדת. לדוגמא, בכדי להשתמש בגישה השנייה כדי לבצע שאילתה שתחזיר את ההודעות האחרונות ביחד עם התגובות שלהם, אנו יכולים להגדיר את הקישור של `comments` במחלקה של `Post` בצורה הבאה,

~~~
[php]
public function relations()
{
    return array(
        'comments' =» array(self::HAS_MANY, 'Comment', 'post_id', 'together'=»false),
    );
}
~~~

כמו כן אנו יכולים להגדיר אפשרות זו בצורה דינאמית בזמן שאנו מבצעים את הטעינה הנלהבת:

~~~
[php]
$posts = Post::model()-»with(array('comments'=»array('together'=»false)))-»findAll();
~~~

» Note|הערה: בגרסאות 1.0, התנהגות ברירת המחדל של Yii תייצר ותריץ `N+1` שאילתות SQL אם ישנם `N` , שאילתות מקושרות מסוג `HAS_MANY` או `MANY_MANY`. כל קישור מסוג `HAS_MANY` או `MANY_MANY` מריץ שאילתה נפרדת. על ידי קריאה למתודה `()together` אחרי `()with`, אנו יכולים לאלץ יצירה והרצה של שאילתת SQL אחת בלבד. לדוגמא,
» ~~~
» [php]
» $posts=Post::model()-»with(
»     'author.profile',
»     'author.posts',
»     'categories')-»together()-»findAll();
» ~~~
»


שאילתות סטטיסטיות
-----------------

» Note|הערה: שאילתות סטטיסטיות נתמכות מגרסאות 1.0.4 ומעלה.

מלבד השאילתות המקושרות המתוארות למעלה, Yii תומכת במה שנקרא שאילתות סטטיסטיות (או שאילתות מצטברות). זה מתייחס לקבלת המידע המצטבר בנוגע לאובייקט המקושר, כמו מספר התגובות לכל הודעה, ממוצע דירוג לכל מוצר, וכדומה. שאילתות סטטיסטיות ניתנות לביצוע על אובייקטים המקושרים בעזרת `HAS_MANY` (לדוגמא, הודעה מכילה הרבה תגובות) או `MANY_MANY` (לדוגמא, הודעה שייכת לכמה קטגוריות וקטגוריה מכילה כמה הודעות).

ביצוע שאילתות סטטיסטיות דומה מאוד לביצוע שאילתה מקושרת כפי שתארנו קודם לכן. ראשית עלינו להגדיר את השאילתה הסטטיסטית במתודה [()relations|CActiveRecord::relations]  של המחלקה [CActiveRecord] כפי שאנו עושים בשאילתות מקושרות.

~~~
[php]
class Post extends CActiveRecord
{
    public function relations()
    {
        return array(
            'commentCount'=»array(self::STAT, 'Comment', 'post_id'),
            'categoryCount'=»array(self::STAT, 'Category', 'post_category(post_id, category_id)'),
        );
    }
}
~~~

בדוגמא למעלה, אנו מגדירים שני שאילתות סטטיסטיות: `commentCount` המחשב את מספר התגובות השייכות להודעה, ו `categoryCount` המחשב את מספר הקטגוריות שהודעה שייכת אליו. זכור שהקישור בין `Post` ו `Comment` הוא `HAS_MANY`, בזמן שהקישור בין `Post` לבין `Category` הוא `MANY_MANY` (עם הטבלה המאחדת `post_category`). כפי שניתן לראות, ההגדרה דומה מאוד לזו של הקישורים שהסברנו לגביהם בחלקים הקודמים. ההבדל היחידי במקרה הזה הוא שסוג הקישור הינו `STAT`.

בעזרת ההגדרה למעלה, אנו יכולים לקבל את מספר התגובות לכל הודעה בעזרת הביטוי הבא `post-»commentCount$`. כשניגש למאפיין זה בפעם הראשונה, תתבצע שאילתת SQL במיוחד לקבלת הנתון הזה. כפי שאנו כבר יודעים, גישה זו נקראת *טעינה עצלה*. אנו יכולים להשתמש *בטעינה נלהבת* במידה ואנו רוצים לדעת את כמות התגובות לכמה הודעות:

~~~
[php]
$posts=Post::model()-»with('commentCount', 'categoryCount')-»findAll();
~~~

הקוד למעלה יבצע 3 שאילתות בכדי לקבל את כל ההודעות ביחד עם מספר התגובות שלהם ומספר הקטגוריות. שימוש בגישה של טעינה עצלה, אנו נבצע `1+N*2` שאילתות SQL אם ישנם `N` הודעות.

כברירת מחדל, שאילתה סטטיסטית תבצע את הביטוי  `COUNT` (ולכן תתקבל מספר התגובות ומספר הקטגוריות בדוגמא למעלה). אנו יכולים לשנות זאת על ידי הגדרת אפשרויות נוספות בזמן שאנו מצהירים זאת במתודה [()relations|CActiveRecord::relations]. האפשרויות הזמינות מסוכמות למטה.

- `select` : הביטוי הסטטיסטי. כברירת מחדל הוא מוגדר כ `COUNT(*)`, האומר ספירה של תתי אובייקטים.

- `defaultValue` : הערך שיש להגדיר לרשומות שלא מקבלות תוצאה מהשאילתה.

- `condition`: סעיף ה `WHERE`. ברירת מחדל הוא ריק.

- `params`: הפרמטרים שצריכים להתחם לשאילתת ה SQL. יש להגדיר אפשרות זו כמערך של מפתחות וערכים.

- `order` : סעיף ה `ORDER BY`. כברירת מחדל הוא ריק.

- `group`: סעיף ה `GROUP BY`. כברירת מחדל הוא ריק.

- `having`: סעיף ה `HAVING`. כברירת מחדל הוא ריק.


שאילתות מקושרות עם מרחבים מוגדרים
----------------------------------

» Note|הערה: תמיכה במרחבים מוגדרים קיימת החל מגרסאות 1.0.5 ומעלה.

שאילתה מקושרת ניתנת לביצוע גם בצירוף של [מרחבים מוגדרים](/doc/guide/database.ar#named-scopes). היא מגיעה בשני מצבים. במצב הראשון, מרחבים מוגדרים מצורפים למודל הראשי. במצב השני, מרחבים מוגדרים מצורפים למודלים המקושרים.

הקוד הבא מציג כיצד לצרף מרחב מוגדר למודל הראשי.

~~~
[php]
$posts=Post::model()-»published()-»recently()-»with('comments')-»findAll();
~~~

זה דומה מאוד לשאילתות לא מקושרות. ההבדל היחידי הוא שישנו קריאה למתודה `()with` מיד לאחר השרשור של המרחב המוגדר. השאילתה למעלה תחזיר את ההודעות שפורסמו לאחרונה ביחד עם התגובות שלהם.

והקוד הבא מציג כיצד לצרף מרחב מוגדר למודל מקושר.

~~~
[php]
$posts=Post::model()-»with('comments:recently:approved')-»findAll();
~~~

השאילתה למעלה תחזיר את כל ההודעות ביחד עם התוגובת שאושרו. זכור ש `comments` מתייחס לשם הקישור, בזמן ש `recently` ו `approved` מתייחס למרחב מוגדר שקיים במחלקת המודל של `Comment`. שם הקישור והמרחבים המוגדרים צריכים להיות מופרדים בעזרת נקודותיים ( : ).

ניתן לצרף מרחבים מוגדרים גם באפשרות של `with` בהגדרת הקישור במתודה [CActiveRecord::relations()]. בדוגמא הבאה, ברגע שאנו ניגשים למאפיין `user-»posts$`, הוא מחזיר את כל התגובות *המאושרות* של ההודעות.

~~~
[php]
class User extends CActiveRecord
{
    public function relations()
    {
        return array(
            'posts'=»array(self::HAS_MANY, 'Post', 'author_id',
                'with'=»'comments:approved'),
        );
    }
}
~~~

» Note|הערה: מרחבים מוגדרים המצורפים למודלים מקושרים צריכים להיות מוגדרים ב [CActiveRecord::scopes]. כתוצאה מכך לא ניתן להגדיר להם פרמטרים.

«div class="revision"»$Id: database.arr.txt 2069 2010-01-08 05:08:29Z qiang.xue $«/div»